UART Custom Adapters

The UART custom protocols

The UART custom adapters use two protocols - PCustomUartControl and PCustomUartCommunication.

The PCustomUartControl protocol class is responsible for the actor configuration and consists of the following messages:

  • configureUart(DCustomUartConfiguration) - Configure UART communication. For details see the Part 3 - Configuring the UART custom actor.

  • enableReceiveData() - Start data reception.

  • disableReceiveData() - Stop data reception. Depending on the underlying implementation the next received packet might still be delivered.

  • configurationComplete() - Reply once the configuration is completed.

  • bufferOverflow() - Dispatched when a buffer overflow error occurs.

The data from the UART interface is transferred over the PCustomUartCommunication protocol class.

  • sendPacket(DCustomUartNotification) - Send a data package.

  • freeRxBuffer(uint8 ref) - Tell the adapter to free the buffer pointed by the pointer in the message.

  • receivedPacket(DCustomUartNotification) - Notify when a data packet is received. The received length will be stored in the dataLength field of the transition data.

  • sendDone() - Response to sendPacket.

The transmitted and received packets are contained in the DCustomUartNotification data class, which consists of the following data types:

  • bufferPtr: a pointer to uint8 rx/tx buffer

  • dataLength: uint32 length of the data pointed by the bufferPtr.

Recommendations

While the UART custom adapters may be used directly from within CaGe (using the PCustomUartControl and PCustomUartCommunication protocol) we strongly recommend to create a wrapping or intermediate actor which handles message en- and decoding. This keeps the test cases free from en-/decoding/crc/…​-logic. We also recommend that the actor handles the UART configuration. An example structure could look like this:

Diagram

Usage

Diagram

The chunk handler

The data received in the receive buffer at first goes through the chunk handler. The user can implement there the first stage of the received data handling, as shown in the examples below (Part 1 - Standard connection to the low-level code and Part 2 - Pre-filtering in the chunk handler). The chunk handler is basically a callback function which is called under two conditions:

  1. If a UART receiver timeout occurs (i.e. when the bus has been idle for the configured number of bit times)

  2. If the last set chunk size was reached

It must return an integer value with the following semantics:

  • If returnValue > 0: Receive the next chunk with length = returnValue bytes (or until receiver timeout)

  • If returnValue <= 0: The current buffer should be split at the returned offset (based on the last received byte). The side before the split is considered to be a complete message. The side after the split is the start of the next message.

    • Examples:

      • if 0 is returned the whole buffer is a message

      • if -5 is returned the last 5 byte of the current buffer will be part of the next message. All bytes before that belong to the completed message.

    • it is not possible to discard buffers from within the chunk handler. You may however return 0 to switch buffers. The message may then be discarded within the receiving actor.

Important

The chunk handler is running on interrupt level. It should therefore not do too much work. You should do the heavy lifting once the message is delivered to the model (room actor transition).

The Example Code

The following sections describe how to use the UART custom adapter to implement an example user protocol. Parts 1 to 3 focus on the actor’s configuration. Part 4 contains the full actor implementation, including a basic message handling.

Part 1 - Standard connection to the low-level code

The first step in configuring the UART actor is to define receive and transmit buffers, as well as the previously mentioned chunk handler - i.e. the callback function that pre-filters the incoming bytes. In this example the bytes are not checked, but instead will be handed over directly to the actor containing the chunk handler. The actor receives the bytes in the transition data of the receivedPacket message.

The buffers declared in the code below are used later in the ROOM actor.

Important

All the buffers must be declared in the DMA memory section by using the "attribute" directive, as shown in the example. Otherwise the low-level code will NOT work!

    Structure {

        usercode1 '''
            #include "uart/Uart.h"      // contains standard UART definitions
        '''
        usercode3 '''

            // Initialize 6 receive buffers, each 255 byte long.
            // Initialize 1 txBuffer that is 50 byte long.

            static uint8_t __attribute__((section(".dmaMemSection_D2"))) rxBuffers[6][255];
            static uint8_t __attribute__((section(".dmaMemSection_D2"))) txBuffer[50];

            static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
            // This function is called when a timeout
			// in data reception occurs or when the receive buffer is full.
                return 0;
            }
        '''

        ...

    }

Part 2 - Pre-filtering in the chunk handler

As mentioned in Example 1, the chunk handler can do some pre-filtering of the incoming data. In this simple example, the chunk handler tries to find the "new line" character "\n" in the current buffer. If the function finds "\n", it computes how many characters are still left in the buffer. It then returns either a 0 or a negative number (number of the excessive bytes). However, if no "\n" character is found, the chunk handler returns 20 - a positive number telling the low-level logic to receive the next 20 bytes and the receivedPacket message is NOT sent.

    static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
        for (uint8_t i = 0; i < newDataLength; i++) {
            if (buffer[i] == '\n') {
                return i - newDataLength;
            }
        }
        return 20; // read the next 20 bytes
    }

Part 3 - Configuring the UART custom actor

An actor is configured by the DCustomUartConfiguration data class, which contains the following fields:

  • uartConfig - it is a structure consisting of the standard UART parameters:

    • uartBaudrate

    • uartParity

    • uartStopbits

    • uartDatabits

  • rxBufferPtrs - contains up to 6 pointers to the receive buffers

  • noOfRxBuffers - number of the above buffers that are actually used

  • rxBufferLength - if the number of the received bytes is equal to this number, the chunk handler is called and the receive buffers are switched

  • initialChunkSize - after this amount of received bytes, the chunk handler will be called for the first time. The next calls occur according to the values returned by the chunk handler function.

  • nextChunkHandler - pointer to the chunk handler function

  • receiverTimeout - after this time (the time unit is the bit duration of the current baud rate) the chunk handler will be called if no data is received

An example UART configuration is shown below.

DCustomUartConfiguration cfg = {
        .uartConfig = {
            .uartBaudrate = 115200,
            .uartParity = EParity_NONE,
            .uartStopbits = EStopBits_STOPBITS_1,
            .uartDatabits = EUartDataBits_DATA_BITS_8,
        },
        .rxBufferPtrs = {rxBuffers[0], rxBuffers[1], rxBuffers[2], rxBuffers[3], rxBuffers[4], rxBuffers[5]},
        .noOfRxBuffers = 6,
        .rxBufferLength = 255,
        .initialChunkSize = 255,
        .nextChunkHandler = chunkHandler,
        .receiverTimeout = 1*9
    };

    // send the configuration message over a PCustomUartControl port
    uartControl.configureUart(&cfg);
Important

When the configuration is complete, the actor sends a configurationComplete message over the PCustomUartControl port. Afterwards, the user should send enableReceiveData message to enable the receiver.

Part 4 - Putting it all together - the complete ROOM actor

In this example we use two UART interfaces that talk to each other. The first interface consists of three actors - the low level UART adapter (UART 1 custom), the intermediate actor and the user protocol control logic. The user protocol control logic actor can be replaced by a CaGe test actor.

The other interface is intended to represent a Device Under Test (DUT) and consists of two actors - a tester actor that plays back a number of messages and the low level UART adapter (UART 7 custom).

structure

The wiring

The required wiring for the examples is simple - you only need to connect the corresponding RX and TX signals. We chose UART 1 and UART 7 but you can choose any of the available UART custom actors.

Table 1. The required wiring for the examples
UART 1 UART 7

PB7 (RX)

PE8 (TX)

PA9 (TX)

PB3 (RX)

The intermediate actor

Let us start with the intermediate actor that contains the elements from the previous examples. It is responsible for configuring the UART interface, implementing the chunk handler function, declaring the DMA buffers and encoding/decoding of the data.

In this example, the single "messages" are separated by the # character. The end of data packet is signalized by the "\n" character. The actor receives the data that comes via the UART 1 port, parses it by removing the delimiters and passes it to the upper layer (user protocol control logic). The actor communicates with the user protocol control logic (see The protocol control logic) over the PMyCustomProtocol class (described in Using custom protocol classes).

Important

The buffer containing the received data should be freed by sending the freeRxBuffer message with the pointer to that buffer. Otherwise a warning will pop-up in the miniHIL’s log output.

	ActorClass AIntermediateActor {
		Interface {
			Port uartInterface: PMyCustomProtocol
			conjugated Port uartCom: PCustomUartCommunication
			conjugated Port uartControl: PCustomUartControl
		}

		Structure {

			usercode1 '''
				#include "uart/Uart.h"
			'''
			usercode3 '''

				// Initialize 6 receive buffers, each 255 byte long.
				// Initialize 1 txBuffer that is 50 byte long.
				// Note that the buffers have to be in the DMA memory section.
				static uint8_t __attribute__((section(".dmaMemSection_D2"))) rxBuffers[6][255];
				static uint8_t __attribute__((section(".dmaMemSection_D2"))) txBuffer[50];

				static uint8_t dataBuffer[100];

				static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
					for (uint8_t i = 0; i < newDataLength; i++) {
						if (buffer[i] == '\n') {
							return i - newDataLength;
						}
					}
					return 20; // read the next 20 bytes
				}
			'''

			external Port uartCom
			external Port uartControl
			external Port uartInterface
			SAP logger: PLogger
			SAP timer: PTimer
		}
		Behavior {
			StateMachine {
				State running
				Transition init0: initial -> delay {
					action '''timer.startTimeout(500);'''
				}
				Transition tr0: running -> running {
					triggers {
						<sendData: uartInterface>
					}
					action '''

						for(int i=0; i<transitionData->dataLength; i++) {
							txBuffer[i] = transitionData->bufferPtr[i];
						}

						DCustomUartNotification pkt = {
							.bufferPtr = txBuffer,
							.dataLength = transitionData->dataLength,
						};

						uartCom.sendPacket(&pkt);
					'''
				}
				Transition tr1: running -> running {
					triggers {
						<receivedPacket: uartCom>
					}
					action '''

						uint8_t ctr=0;

						// init the data structure that will be sent later
						DCustomUartNotification pkt = {
							.bufferPtr = 0,
							.dataLength = 0,
						};
						ctr=0;
						// This place in code is for additional or
						// computationally expensive message parsing.

						// extract single messages from the buffer
						while(ctr<transitionData->dataLength){
							pkt.bufferPtr = (dataBuffer+ctr);
							while(ctr<transitionData->dataLength){
								// check if the current character is the delimiter
								if(transitionData->bufferPtr[ctr] != '#') {
									dataBuffer[ctr]=transitionData->bufferPtr[ctr];
									ctr++;
								}
								else {
									pkt.dataLength = ctr;
									ctr++;
									break;
								}
							}
							uartInterface.receivedData(&pkt);
						}
						uartCom.freeRxBuffer(transitionData->bufferPtr);		// free the buffer
					'''
				}
				Transition tr7: running -> running {
					triggers {
						<bufferOverflow: uartControl>
					}
					action '''logger.log("UART: Buffer overflow!");'''
				}
				State configuration
				State delay
				Transition tr3: configuration -> running {
					triggers {
						<configurationComplete: uartControl>
					}
					action '''uartControl.enableReceiveData();
							  uartInterface.uartReady();'''
				}
				Transition tr4: delay -> configuration {
					triggers {
						<timeout: timer>
					}
					action '''
						DCustomUartConfiguration cfg = {
						        .uartConfig = {
						            .uartBaudrate = 115200,
						            .uartParity = EParity_NONE,
						            .uartStopbits = EStopBits_STOPBITS_1,
						            .uartDatabits = EUartDataBits_DATA_BITS_8,
						        },
						        .rxBufferPtrs = {rxBuffers[0], rxBuffers[1], rxBuffers[2], rxBuffers[3], rxBuffers[4], rxBuffers[5]},
						        .noOfRxBuffers = 6,
						        .rxBufferLength = 255,
						        .initialChunkSize = 255,
						        .nextChunkHandler = chunkHandler,
						        .receiverTimeout = 1*9
						    };

						uartControl.configureUart(&cfg);
					'''
				}
			}
		}
	}

Using custom protocol classes

The user protocol control logic usually requires a custom protocol class. The protocol class used in the examples is shown in the code snippet below. Custom protocol classes allow to structure your data and enable a convenient way to connect to CaGe.

// This is an example protocol. You can define your own protocol(s).

	ProtocolClass PMyCustomProtocol {
		incoming {
			Message sendData(DCustomUartNotification)
		}

		outgoing {
			Message receivedData(DCustomUartNotification)
			Message uartReady()
		}
	}

The protocol control logic

This code snippet contains the protocol control logic. In this example the protocol handling consists merely of printing the received data in the log output and sending some ASCII data to show general principle of operation. In a real test scenario, this actor would be replaced with a CaGe test actor.

	ActorClass AProtocolControlLogicActor {

		Interface {
			conjugated Port uartInterface: PMyCustomProtocol
		}
		Structure {
			Attribute msgBuffer [50]: uint8
			external Port uartInterface
			SAP timer: PTimer
			SAP logger: PLogger
		}

		Behavior {
			StateMachine {
				State waitForUartReady
				State running
				Transition init0: initial -> waitForUartReady {
					action '''
						timer.startTimer(2000);
					'''
				}
				Transition tr0: waitForUartReady -> running {
					triggers {
						<uartReady: uartInterface>
					}
				}
				Transition tr1: running -> running {
					triggers {
						<receivedData: uartInterface>
					}
					action '''
                        // You can add your protocol handling here. The below lines only print
                        // the received data into the logger.

                        // zero terminate received string and forward it to logger
                        *(transitionData->bufferPtr + transitionData->dataLength) = '\0';
                        logger.log((char *)(transitionData->bufferPtr));'''
				}
				Transition tr2: running -> running {
					triggers {
						<timeout: timer>
					}
					action '''
						// get the length of the example string, put the contents in the msgBuffer
						uint16_t length = snprintf((char *)msgBuffer, 255, "Some test data to be sent\n");
						// check if snprintf returned successfully
						if (length > 0 && length < 256) {
							// prepare the data structure to send
						    DCustomUartNotification sendReq = {
						        .bufferPtr = msgBuffer,
						        .dataLength = length
						    };
						    // send the data
						    uartInterface.sendData(&sendReq);
						} else {
						    logger.log("CustomUartTester: Could not snprintf...");
						}
					'''
				}
			}
		}
	}

The Device Under Test actor

The simple implementation below uses another miniHIL’s UART adapter (UART 7) to implement DUT that sends data over UART periodically and in an alternating manner. The single "messages" are separated by the # character. The end of a data packet is signalized by the "\n" character.

ActorClass ACustomUartTester {
		Interface {
			conjugated Port uartCtrl: PCustomUartControl
			conjugated Port uartCom: PCustomUartCommunication
		}
		Structure {
			usercode1 '''
				#include "uart/Uart.h"
			'''
			usercode3 '''

				static uint8_t __attribute__((section(".dmaMemSection_D2"))) rxBuffers[2][256];
				static uint8_t __attribute__((section(".dmaMemSection_D2"))) txBuffer[256];

				static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
					for (uint8_t i = 0; i < newDataLength; i++) {
						if (buffer[i] == '\n') {
							return i - newDataLength;
						}
					}
					return 20; // read the next 20 bytes
				}
			'''
			external Port uartCom
			external Port uartCtrl
			Attribute toggleMsg: uint8 = "0"

			SAP timer: PTimer
			SAP logger: PLogger
		}
		Behavior {

			StateMachine {
				State configuring
				State active
				Transition init0: initial -> delay {
					action '''timer.startTimeout(500);'''
				}
				Transition tr0: configuring -> active {
					triggers {
						<configurationComplete: uartCtrl>
					}
					action '''
						timer.startTimeout(2000);
						uartCtrl.enableReceiveData();'''
				}
				Transition tr1: active -> active {
					triggers {
						<receivedPacket: uartCom>
					}
					action '''
						// zero terminate received string and forward it to logger
						*(transitionData->bufferPtr + transitionData->dataLength) = '\0';
						logger.log((char *)(transitionData->bufferPtr));
						uartCom.freeRxBuffer(transitionData->bufferPtr);'''
				}
				Transition tr2: active -> active {
					triggers {
						<timeout: timer>
					}
					action '''
						// simulate a pause in the bytestream
						timer.startTimeout(1000);

						uint16_t length;

						// single messages are separated by '#'
						if (toggleMsg == 0) {
							length = snprintf((char *)txBuffer, 256, "Test message1#Test message2#Test message3\nTest message4#Tes");
							toggleMsg = 1;
						}
						else {
							length = snprintf((char *)txBuffer, 256, "t message5#Test message6\n");
							toggleMsg = 0;
						}

						if (length > 0 && length < 256) {
						    DCustomUartNotification sendReq = {
						        .bufferPtr = txBuffer,
						        .dataLength = length
						    };
						    uartCom.sendPacket(&sendReq);
						} else {
						    logger.log("CustomUartTester: Could not snprintf...");
						}
						'''
				}
				State delay
				Transition tr3: delay -> configuring {
					triggers {
						<timeout: timer>
					}
					action '''
						DCustomUartConfiguration cfg = {
						    .uartConfig = {
						        .uartBaudrate = 115200,
						        .uartDatabits = EUartDataBits_DATA_BITS_8,
						        .uartParity = EParity_NONE,
						        .uartStopbits = EStopBits_STOPBITS_1
						    },
						    .rxBufferPtrs = {rxBuffers[0], rxBuffers[1]},
						    .noOfRxBuffers = 2,
						    .rxBufferLength = 256,
						    .initialChunkSize = 15,
						    .nextChunkHandler = chunkHandler,
						    .receiverTimeout = 5*9  // 5 * 9 Bit
						};
						uartCtrl.configureUart(&cfg);'''
				}
			}
		}
	}